package edu.hawaii.halealohacli.jcev.command;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.xml.datatype.XMLGregorianCalendar;
import org.junit.Test;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.sensordata.jaxb.SensorData;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Tests the methods of the EnergySince class. This test assumes the existence of a functioning
 * WattDepot server at http://server.wattdepot.org:8190/wattdepot. Note that this test will fail if
 * the connection times out while the object for each test is being generated or a data request is
 * being made.
 * 
 * @author Jordan Takayama
 */
public class TestEnergySince {
  
  /**
   * The URL of the WattDepot server.
   */
  private String url = "http://server.wattdepot.org:8190/wattdepot/";
  
  /**
   * The name of the Source associated with one of the Hale Aloha towers.
   */
  private String tower = "Ilima";
  
  /**
   * A WattDepot client which connects to the server.
   */
  private WattDepotClient sampleClient = new WattDepotClient(url);
  
  /**
   * The class being tested.
   */
  private Class<EnergySince> testClass = EnergySince.class;
  
  /**
   * An EnergySince object which is initialized with specific data for each test case.
   */
  private EnergySince testObject = null;
  
  /**
   * A Date representing the current date.
   */
  private Date current = new Date();
  
  /**
   * Parses Dates into the simple date format used by WattDepot.
   */
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
  
  /**
   * A string representing the name of the method "setAll", which this class frequently invokes.
   */
  private String setAll = "setAll";

  /**
   * Generates new EnergySince objects for testing. These objects request data for the previous day.
   * 
   * @param source The name of the tower for which energy consumption is being measured.
   * @param client The WattDepotClient used to request data from the server.
   * @return An EnergySince object.
   * @throws Exception If an internal method call throws an Exception.
   */
  private EnergySince makeEnergySince(String source, WattDepotClient client) throws Exception {
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, String.class, String.class, WattDepotClient.class);
    runSetAll.setAccessible(true);
    
    // Get a string in yyyy-MM-dd format representing yesterday relative to today.
    Calendar calendar = Calendar.getInstance(Locale.getDefault());
    String dateString = generateYesterday(calendar);

    // Generate a sample EnergySince object in order to invoke its method.
    EnergySince result = new EnergySince();
    result = (EnergySince) runSetAll.invoke(result, tower, dateString, sampleClient);
    return result;
  }

  /**
   * Tests the execute method by intentionally triggering its exceptions.
   * 
   * @throws NoSuchMethodException If the execute method does not exist.
   */
  @Test
  public void testExecute() throws NoSuchMethodException {
    Method testExecute =
        testClass.getDeclaredMethod("execute", String.class, WattDepotClient.class);
    testExecute.setAccessible(true);
    testObject = new EnergySince();
    String message = "Correctly threw exception: ";
    System.out.println("Testing execute:\n");

    try {
      testObject.execute("", sampleClient);
      fail("Failed to throw an exception for input with no arguments.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }
    
    try {
      testObject.execute("Ilima ", sampleClient);
      fail("Failed to throw an exception for a blank second argument.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }
    
    try {
      testObject.execute("Ilima foo bar", sampleClient);
      fail("Failed to throw an exception for more than two arguments.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("Nowhere 2011-11-03", sampleClient);
      fail("Failed to throw an exception for a source which did not exist. ");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("Ilima asdf-gh-jk", sampleClient);
      fail("Failed to throw an exception for a badly formatted second argument.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }
    
    try {
      testObject.execute("Ilima 2011-13-01", sampleClient);
      fail("Failed to throw an exception for invalid date 2011-13-01");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      // The date 9999-12-31 will be in the future for the foreseeable future.
      testObject.execute("Ilima 9999-12-31", sampleClient);
      fail("Failed to throw an exception for future date 9999-12-31");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      // This part of the test should not cause any exceptions to be thrown.
      String currentDate = format.format(current);
      testObject.execute("Ilima " + currentDate, sampleClient);
    }
    catch (Exception e) {
      fail("Threw an exception when given acceptable input: " + e.getMessage());
    }

  }

  /**
   * Creates an EnergySince object with the default constructor, then checks that the returned 
   * object is not null.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testEnergySince() throws Exception {
    testObject = new EnergySince();
    assertNotNull("EnergySince constructor returned null", testObject);
  }
  
  /**
   * Invokes setAll on an EnergySince object, then checks that its fields have been initialized.
   * 
   * @throws NoSuchMethodException If the setAll method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetAll() throws NoSuchMethodException, Exception {
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, String.class, String.class, WattDepotClient.class);
    runSetAll.setAccessible(true);

    // The object used in this test collects data for the previous day.
    String today = generateYesterday(Calendar.getInstance(Locale.getDefault()));
    testObject = new EnergySince();
    testObject = (EnergySince) runSetAll.invoke(testObject, tower, today, sampleClient);
    System.out.format("\nTesting setAll with tower %s for date %s:\n", tower, today);
    assertNotNull("setAll failed to initialize client.", testObject.getClient());
    assertNotNull("setAll failed to initialize data.", testObject.getData());
    assertNotNull("setAll failed to initialize name.", testObject.getName());
    assertNotNull("setAll failed to initialize startTimestamp.", testObject.getStartTimestamp());
    assertNotNull("setAll failed to initialize endTimestamp.", testObject.getEndTimestamp());
    assertNotNull("setAll failed to initialize energyUsed.", testObject.getEnergyUsed());
  }

  /**
   * Checks that the EnergySince object's WattDepotClient is equal to the object defined at the
   * beginning of this test.
   * 
   * @throws NoSuchMethodException If the getClient method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetClient() throws NoSuchMethodException, Exception {
    Method testClient = testClass.getDeclaredMethod("getClient");
    testClient.setAccessible(true);
    testObject = makeEnergySince(tower, sampleClient);

    System.out.println("Testing getClient: ");
    WattDepotClient checkClient = (WattDepotClient) testClient.invoke(testObject);
    assertEquals("Failed to retrieve client equal to original client.", checkClient, sampleClient);
  }

  /**
   * Checks that the EnergySince object's name is equal to the name defined at the beginning of this
   * test.
   * 
   * @throws NoSuchMethodException If the getName method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetName() throws NoSuchMethodException, Exception {
    Method testName = testClass.getDeclaredMethod("getName");
    testName.setAccessible(true);
    testObject = makeEnergySince(tower, sampleClient);

    System.out.println("Testing getName: ");
    String checkName = (String) testName.invoke(testObject);
    assertEquals("Name of tower or lounge is not equal to original name.", checkName, tower);
  }

  /**
   * Checks that getData does not return null.
   * 
   * @throws NoSuchMethodException If the getData method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetData() throws NoSuchMethodException, Exception {
    Method testData = testClass.getDeclaredMethod("getData");
    testData.setAccessible(true);
    testObject = makeEnergySince(tower, sampleClient);

    System.out.println("Testing getData: ");
    SensorData checkData = (SensorData) testData.invoke(testObject);
    assertNotNull("getData returned null.", checkData);
  }

  /**
   * Checks that getStartTimestamp returns an XMLGregorianCalendar equal to one generated with the
   * same start-date information.
   * 
   * @throws Exception If getStartTimestamp does not exist.
   * @throws NoSuchMethodException If a method call within the test throws an exception.
   */
  @Test
  public void testGetStartTimestamp() throws NoSuchMethodException, Exception {
    Method testStart = testClass.getDeclaredMethod("getStartTimestamp");
    Method runGenerateTimestamp =
        testClass
            .getDeclaredMethod("generateTimestamp", String.class, String.class, TimeZone.class);
    testStart.setAccessible(true);
    runGenerateTimestamp.setAccessible(true);
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, String.class, String.class, WattDepotClient.class);
    runSetAll.setAccessible(true);
    String yesterday = generateYesterday(Calendar.getInstance());

    testObject = new EnergySince();
    testObject = (EnergySince) runSetAll.invoke(testObject, tower, yesterday, sampleClient);

    XMLGregorianCalendar originalData =
        (XMLGregorianCalendar) runGenerateTimestamp.invoke(testObject, yesterday, "T00:00:00.000",
            TimeZone.getDefault());

    System.out.println("Testing getStartTimestamp: ");
    XMLGregorianCalendar checkStart = (XMLGregorianCalendar) testStart.invoke(testObject);
    assertEquals("Timestamp returned is not equal to one produced with the same information.",
        originalData, checkStart);
  }

  /**
   * Checks that getEndTimestamp returns an XMLGregorianCalendar equal to one generated with the
   * same end-date information.
   * 
   * @throws NoSuchMethodException If the getStartTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception. This test always fails
   * and throws an InvocationTargetException (because the end timestamp is past the time range of
   * the data available on the server) if it is run within fifteen minutes of midnight, most likely
   * as a result of the time that it takes the server to update.
   */
  @Test
  public void testGetEndTimestamp() throws NoSuchMethodException, Exception {
    Method testEndTimestamp = testClass.getDeclaredMethod("getEndTimestamp");
    testEndTimestamp.setAccessible(true);
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, String.class, String.class, WattDepotClient.class);
    runSetAll.setAccessible(true);

    String dateString = format.format(current);
    String year = dateString.substring(0, 4);
    String month = dateString.substring(5, 7);
    String day = dateString.substring(8, 10);
    testObject = new EnergySince();
    testObject =
        (EnergySince) runSetAll.invoke(testObject, tower, year + "-" + month + "-" + day,
            sampleClient);

    SensorData sameData = testObject.getData();
    XMLGregorianCalendar currentTime = sameData.getTimestamp();

    System.out.println("Testing getEndTimestamp: ");
    XMLGregorianCalendar checkEnd = (XMLGregorianCalendar) testEndTimestamp.invoke(testObject);
    assertEquals("Timestamp returned is not equal to one produced with the same information.",
        currentTime, checkEnd);
  }

  /**
   * Checks that getEnergyUsed returns a known good value when data is collected for a known
   * good time interval. In the event of a WattDepot server crash or other data loss, the dates in 
   * this code may need to be changed to account for the loss of old data logs. The current date 
   * used is 2011-11-23. It is also assumed that the value is formatted to two decimal places 
   * (e.g., 314.15).
   * 
   * @throws NoSuchMethodException If the getEnergyUsed method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetEnergyUsed() throws NoSuchMethodException, Exception {
    Method testEnergyUsed = testClass.getDeclaredMethod("getEnergyUsed");
    Method setStart = testClass.getDeclaredMethod("setStartTimestamp", XMLGregorianCalendar.class);
    Method setEnd = testClass.getDeclaredMethod("setEndTimestamp", XMLGregorianCalendar.class);
    Method setEnergy =
        testClass.getDeclaredMethod("setEnergyUsed", WattDepotClient.class, String.class,
            XMLGregorianCalendar.class, XMLGregorianCalendar.class, int.class);
    testEnergyUsed.setAccessible(true);
    setStart.setAccessible(true);
    setEnd.setAccessible(true);
    setEnergy.setAccessible(true);
    testObject = makeEnergySince(tower, sampleClient);

    String knownDate = "2011-11-23";
    Double knownValue = 618.39;
    XMLGregorianCalendar start = Tstamp.makeTimestamp(knownDate + "T00:00:00.000-10:00");
    XMLGregorianCalendar end = Tstamp.makeTimestamp(knownDate + "T23:59:59.999-10:00");
    setStart.invoke(testObject, start);
    setEnd.invoke(testObject, end);
    setEnergy.invoke(testObject, sampleClient, tower, start, end, 0);

    System.out.println("Testing getEnergyUsed: ");
    Double checkEnergyUsed = (Double) testEnergyUsed.invoke(testObject);
    
    // It should not return output that is null, negative, or different from the known good value.
    assertNotNull("getEnergyUsed returned null.", checkEnergyUsed);
    boolean greaterThanZero = (checkEnergyUsed >= 0);
    assertTrue("getEnergyUsed returned energy consumption < 0.", greaterThanZero);
    assertEquals(
        "Energy use queries for Ilima Tower for " + knownDate + " should return " + knownValue
        + " kilowatt-hours.", knownValue, checkEnergyUsed);
  }

  /**
   * Checks that setClient correctly sets an EnergySince object's uninitialized client.
   * 
   * @throws NoSuchMethodException If the setClient method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetClient() throws NoSuchMethodException, Exception {
    Method testSetClient = testClass.getDeclaredMethod("setClient", WattDepotClient.class);
    testSetClient.setAccessible(true);
    testObject = new EnergySince();

    System.out.println("Testing setClient: ");
    testSetClient.invoke(testObject, sampleClient);
    WattDepotClient setResult = testObject.getClient();
    assertNotNull("setClient failed to set the WattDepotClient.", setResult);
    assertEquals("setClient's set client is not equal to the WattDepotClient passed to it.",
        sampleClient, setResult);
  }

  /**
   * Checks that setName correctly sets an EnergySince object's uninitialized source name.
   * 
   * @throws NoSuchMethodException If the setName method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetName() throws NoSuchMethodException, Exception {
    Method testSetName = testClass.getDeclaredMethod("setName", String.class);
    testSetName.setAccessible(true);
    testObject = new EnergySince();

    System.out.println("Testing setName: ");
    String testName = "Mokihana";
    testSetName.invoke(testObject, testName);
    String setResult = testObject.getName();
    assertNotNull("setName failed to set the name.", setResult);
    assertEquals("setName's set name is not equal to the name passed to it.", testName, setResult);
  }

  /**
   * Checks that setData correctly sets an EnergySince object's uninitialized SensorData.
   * 
   * @throws NoSuchMethodException If the setData method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetData() throws NoSuchMethodException, Exception {
    Method testSetData = testClass.getDeclaredMethod("setData", SensorData.class);
    testSetData.setAccessible(true);
    testObject = new EnergySince();

    System.out.println("Testing setData: ");
    List<Source> sources = sampleClient.getSources();
    SensorData testData = sampleClient.getLatestSensorData(sources.get(0).getName());
    testSetData.invoke(testObject, testData);
    SensorData setResult = testObject.getData();
    assertNotNull("setData failed to set the data.", setResult);
    assertEquals("setData's set SensorData is not equal to the SensorData passed to it.", testData,
        setResult);
  }

  /**
   * Checks that setStartTimestamp correctly sets an EnergySince object's uninitialized
   * startTimestamp.
   * 
   * @throws NoSuchMethodException If the setStartTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetStartTimestamp() throws NoSuchMethodException, Exception {
    Method testSetStart =
        testClass.getDeclaredMethod("setStartTimestamp", XMLGregorianCalendar.class);
    testSetStart.setAccessible(true);
    testObject = new EnergySince();
    
    System.out.println("Testing setStartTimestamp:");
    XMLGregorianCalendar sampleTimestamp = Tstamp.makeTimestamp("2011-11-11T11:11:11.111-11:00");
    testSetStart.invoke(testObject, sampleTimestamp);
    XMLGregorianCalendar setResult = testObject.getStartTimestamp();
    assertNotNull("setStartTimestamp failed to set the XMLGregorianCalendar", setResult);
    assertEquals("setStartTimestamp's set XMLGregorianCalendar is not equal to the " + 
        "XMLGregorianCalendar passed to it.", sampleTimestamp, setResult);
  }
  
  /**
   * Checks that setEndTimestamp correctly sets an EnergySince object's uninitialized EndTimestamp.
   * 
   * @throws NoSuchMethodException If the setEndTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEndTimestamp() throws NoSuchMethodException, Exception {
    Method testSetEnd =
        testClass.getDeclaredMethod("setEndTimestamp", XMLGregorianCalendar.class);
    testSetEnd.setAccessible(true);
    testObject = new EnergySince();
    
    System.out.println("Testing setEndTimestamp:");
    XMLGregorianCalendar sampleTimestamp = Tstamp.makeTimestamp("2011-11-11T11:11:11.111-11:00");
    testSetEnd.invoke(testObject, sampleTimestamp);
    XMLGregorianCalendar setResult = testObject.getEndTimestamp();
    assertNotNull("setEndTimestamp failed to set the XMLGregorianCalendar", setResult);
    assertEquals("setEndTimestamp's set XMLGregorianCalendar is not equal to the " + 
        "XMLGregorianCalendar passed to it.", sampleTimestamp, setResult);
  }
  
  /**
   * Checks that setEnergyUsed correctly sets an EnergySince object's uninitialized EnergyUsed
   * to a known good value for a known good time interval. In the event of a WattDepot server
   * crash or other data loss, the dates in this code may need to be changed to reflect the loss of
   * old data logs. The current date used is 2011-11-23. It is also assumed that the value is 
   * formatted to two decimal places (e.g., 314.15).
   * 
   * @throws NoSuchMethodException If the setEnergyUsed method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEnergyUsed() throws NoSuchMethodException, Exception {
    Method testSetEnergy = testClass.getDeclaredMethod("setEnergyUsed", WattDepotClient.class,
        String.class, XMLGregorianCalendar.class, XMLGregorianCalendar.class, int.class);
    testSetEnergy.setAccessible(true);
    testObject = new EnergySince();
    XMLGregorianCalendar start = Tstamp.makeTimestamp("2011-11-23T00:00:00.000-10:00");
    XMLGregorianCalendar end = Tstamp.makeTimestamp("2011-11-23T23:59:59.999-10:00");
 
    System.out.println("Testing setEnergyUsed for a time interval of 24 hours.");
    testSetEnergy.invoke(testObject, sampleClient, tower, start, end, 0);
    Double setResult = testObject.getEnergyUsed();
    assertNotNull("setEnergyUsed failed to set the double", setResult);
    assertEquals("Energy use for " + tower + " Tower for 2011-11-23 should have been set to 618.39"
        + " kilowatt-hours.", (Double) 618.39, setResult);
    
    XMLGregorianCalendar start2 = Tstamp.makeTimestamp("2011-11-23T00:00:00.000-10:00");
    XMLGregorianCalendar end2 = Tstamp.makeTimestamp("2011-11-23T00:00:00.000-10:00");
    System.out.println("Testing setEnergyused for a time interval of 0 seconds.");
    testSetEnergy.invoke(testObject, sampleClient, tower, start2, end2, 0);
    setResult = testObject.getEnergyUsed();
    assertNotNull("setEnergyUsed failed to set the double.", setResult);
    assertEquals("Energy use for " + tower + " Tower for 2011-11-23 00:00:00.000 to 00:00:00.000 " 
        + " should be 0.00 kWh", (Double) 0.00, setResult); 
  }

  /**
   * Checks that correctly formatted strings representing GMT offsets are being returned by
   * gmtOffsetString.
   * 
   * @throws NoSuchMethodException If the gmtOffsetString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGmtOffsetString() throws NoSuchMethodException, Exception {
    Method testOffset = testClass.getDeclaredMethod("gmtOffsetString", TimeZone.class);
    testOffset.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new EnergySince();

    System.out.println("Testing gmtOffsetString: ");
    // Test for positive offset < 10 hours
    assertEquals("GMT offset for Japan (JST) should be +09:00", "+09:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("JST"))));
    // Test for positive offset > 10 hours
    assertEquals("GMT offset for New Zealand (NZ) should be +12:00", "+12:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("NZ"))));
    // Test for 0 hours
    assertEquals("GMT offset for GMT should be +00:00", "+00:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("GMT"))));
    // Test for negative offset < 10 hours
    assertEquals("GMT offset for Pacific Standard Time (PST) should be -08:00", "-08:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("PST"))));
    // Test for negative offset > 10 hours
    assertEquals("GMT offset for Hawaiian Standard Time (HST) should be -10:00", "-10:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("HST"))));
  }

  /**
   * Check that XMLGregorianCalendars with correct information are being returned by
   * generateTimestamp.
   * 
   * @throws NoSuchMethodException If the generateTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGenerateTimestamp() throws NoSuchMethodException, Exception {
    Method testGenerateTimestamp =
        testClass
            .getDeclaredMethod("generateTimestamp", String.class, String.class, TimeZone.class);
    testGenerateTimestamp.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new EnergySince();

    System.out.println("Testing generateTimestamp: ");
    TimeZone testZone = TimeZone.getTimeZone("PST");
    XMLGregorianCalendar testCalendar =
        (XMLGregorianCalendar) testGenerateTimestamp.invoke(testObject, "2009-04-13",
            "T00:00:00.000", testZone);
    XMLGregorianCalendar compareCalendar = Tstamp.makeTimestamp("2009-04-13T00:00:00.000-08:00");
    assertEquals("generateTimestamp should make a timestamp logically equal to a "
        + "timestamp generated based on the same information", compareCalendar, testCalendar);
    // Test that the exception is correctly thrown.
    try {
      XMLGregorianCalendar failure = (XMLGregorianCalendar) testGenerateTimestamp.invoke(testObject,
          "2001-december-eighth", "T00:00:00.000", testZone);
      fail("Failed to throw exception for incorrectly formatted date: " + failure.toString());
    }
    catch (Exception e) {
      System.out.println("Correctly threw exception: " + e.getMessage());
    }
  }

  /**
   * Check that the date format is correctly being tested by isDateFormatCorrect.
   * 
   * @throws NoSuchMethodException If the isDateFormatCorrect method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testIsDateFormatCorrect() throws NoSuchMethodException, Exception {
    Method testDateFormat = testClass.getDeclaredMethod("isDateFormatCorrect", String.class);
    testDateFormat.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new EnergySince();

    System.out.println("Testing isDateFormatCorrect: ");
    // Test incorrect date formats
    assertFalse("11-12-11 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "11-12-11"));
    assertFalse("November 12 2011 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "November 12 2011"));
    assertFalse("November-12-2011 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "November-12-2011"));
    assertFalse("2011-Nov-12 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "2011-Nov-12"));
    assertFalse("2011-12-Eleven is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "2011-12-Eleven"));
    assertFalse("####-##-## does not use digits for yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "####-##-##"));
    // Test correct date format
    assertTrue("2011-11-12 is in the format yyyy-mm-dd and should pass.",
        (Boolean) testDateFormat.invoke(testObject, "2011-11-12"));
  }

  /**
   * Checks that inCalendarRanges is not accepting incorrect dates.
   * 
   * @throws NoSuchMethodException If the inCalendarRanges method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testInCalendarRanges() throws NoSuchMethodException, Exception {
    Method testRanges = testClass.getDeclaredMethod("inCalendarRanges", String.class);
    testRanges.setAccessible(true);
    // The method manipulates no fields in the below EnergySince object; setAll is not needed.
    testObject = new EnergySince();

    System.out.println("Testing inCalendarRanges: ");
    // Test incorrect dates
    assertFalse("1998-10-56 is an invalid date.",
        (Boolean) testRanges.invoke(testObject, "1998-10-56"));
    assertFalse("2000-18-11 is an invalid date.",
        (Boolean) testRanges.invoke(testObject, "2000-18-11"));
    assertFalse("1933-02-29 is an invalid date (no leap year).",
        (Boolean) testRanges.invoke(testObject, "1933-02-29"));
    assertFalse("1900-02-29 is an invalid date (no leap year if divisible by 100 but not by 400).",
        (Boolean) testRanges.invoke(testObject, "1911-02-29"));
    assertTrue("2000-02-29 is a valid date (leap year).",
        (Boolean) testRanges.invoke(testObject, "2000-02-29"));
    assertTrue("2009-01-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-01-31"));
    assertTrue("2009-02-28 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-02-28"));
    assertTrue("2009-03-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-03-31"));
    assertTrue("2009-04-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-04-30"));
    assertTrue("2009-05-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-05-22"));
    assertTrue("2009-06-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-06-30"));
    assertTrue("2009-07-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-07-31"));
    assertTrue("2009-08-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-08-31"));
    assertTrue("2009-09-30 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-09-30"));
    assertTrue("2009-10-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-10-31"));
    assertTrue("2009-11-30 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-11-30"));
    assertTrue("2009-12-31 is a valid date.",
        (Boolean) testRanges.invoke(testObject, "2009-12-31"));
  }

  /**
   * Checks that notInFuture is correctly identifying dates as not being in the future.
   * 
   * @throws NoSuchMethodException If the notInFuture method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testNotInFuture() throws NoSuchMethodException, Exception {
    Method testFuture = testClass.getDeclaredMethod("notInFuture", String.class);
    testFuture.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new EnergySince();

    // Get the current date as a string, and parse out the date using yyyy-MM-dd format.
    String dateString = format.format(current);
    String futureMessage = " is in the future and should not pass.";
    String dateMessage = "Date ";
    int year = Integer.parseInt(dateString.substring(0, 4));
    int month = Integer.parseInt(dateString.substring(5, 7));
    String defaultDay = "16";
    String monthString = Integer.toString(month);
    String testDateString;

    System.out.println("Testing notInFuture:");
    // Test an arbitrary date in the past.
    testDateString = "1969-07-20";
    assertTrue(dateMessage + testDateString + " is in the past and should pass.",
        (Boolean) testFuture.invoke(testObject, testDateString));
    // Test a date one month in the past.
    if (month > 1) {
      if ((month - 1) < 10) {
        testDateString = year + "-0" + (month - 1) + "-" + defaultDay; 
      }
      else {
        testDateString = year + (month - 1) + "-" + defaultDay;
      }
    }
    else if (month == 1) {
      testDateString = year + "-12-" + defaultDay;
    }
    
    // Test today.
    testDateString = dateString;
    assertTrue(dateMessage + testDateString + " is the present day and should pass.",
        (Boolean) testFuture.invoke(testObject, testDateString));
    // Test a date from next year.
    testDateString = (year + 1) + "-" + monthString + "-" + defaultDay;
    assertFalse(dateMessage + testDateString + futureMessage,
        (Boolean) testFuture.invoke(testObject, testDateString));
    // Test a date about a month in the future. If necessary, roll over to January of the next year.
    if (month == 12) {
      testDateString = (year + 1) + "-01-" + dateString.substring(8, 10);
      assertFalse(dateMessage + testDateString + futureMessage,
          (Boolean) testFuture.invoke(testObject, testDateString));
    }
    else if ((month + 1) < 10) {
      testDateString = year + "-0" + (month + 1) + "-" + defaultDay;
    }
    else {
      testDateString = year + "-" + (month + 1) + "-" + defaultDay;
    }
  }

  /**
   * Check that addPrecedingZeroes is correctly adding preceding zeroes to strings.
   * 
   * @throws NoSuchMethodException If the addPrecedingZeroes method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testAddPrecedingZeroes() throws NoSuchMethodException, Exception {
    Method testAddZeroes = 
        testClass.getDeclaredMethod("addPrecedingZeroes", String.class, int.class);
    testAddZeroes.setAccessible(true);
    testObject = new EnergySince();
    String message = "testAddZeroes should convert ";
    String to = " to ";
    String testString;
    String resultString;
    String expectedString;
    
    System.out.println("Testing addPrecedingZeroes:");
    // Lengthen it to two digits.
    testString = "9";
    resultString = (String) testAddZeroes.invoke(testObject, testString, 2);
    expectedString = "09";
    assertEquals(message + testString + to + expectedString + ".", 
        expectedString, resultString);
    // Increasing something to the same number of digits should have no effect.
    testString = "10";
    resultString = (String) testAddZeroes.invoke(testObject, testString, 2);
    expectedString = "10";
    assertEquals(message + testString + to + expectedString + ".", 
        expectedString, resultString);
    // Lengthen it to three digits.
    testString = "10";
    resultString = (String) testAddZeroes.invoke(testObject, testString, 3);
    expectedString = "010";
    assertEquals(message + testString + to + expectedString + ".", 
        expectedString, resultString);
    // Lengthen a negative number to three digits.
    testString = "-10";
    resultString = (String) testAddZeroes.invoke(testObject, testString, 3);
    expectedString = "-010";
    assertEquals(message + testString + to + expectedString + ".", 
        expectedString, resultString);
    // Test that exceptions are thrown for incorrectly formatted input values.
    try {
      testString = "-AA";
      resultString = (String) testAddZeroes.invoke(testObject, testString, 2);
      fail("addZeroes should throw an exception when given non-integer input.");
    }
    catch (Exception e) {
      System.out.println("Correctly threw exception when given non-digit input.:\n"
          + e.getMessage());
    }
    
    try {
      testString = "--10";
      resultString = (String) testAddZeroes.invoke(testString, 2);
      fail("addZeroes should throw an exception when given input with multiple '-' signs.");
    }
    catch (Exception e) {
      System.out.println("Correctly threw exception when given non-digit input:\n" 
          + e.getMessage());
    }
  }
  
  /**
   * Checks that toString does not return null.
   * 
   * @throws NoSuchMethodException If the toString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testToString() throws NoSuchMethodException, Exception {
    Method testToString = testClass.getDeclaredMethod("toString");
    testToString.setAccessible(true);
    testObject = makeEnergySince(tower, sampleClient);

    System.out.println("Testing toString: ");
    String checkToString = (String) testToString.invoke(testObject);
    assertNotNull("toString returned null.", checkToString);
  }

  /**
   * Checks that getHelpString does not return null.
   * 
   * @throws NoSuchMethodException If the getHelpString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetHelpString() throws NoSuchMethodException, Exception {
    Method testHelpString = testClass.getDeclaredMethod("getHelpString");
    testHelpString.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new EnergySince();

    System.out.println("Testing getHelpString: ");
    String checkHelp = (String) testHelpString.invoke(testObject);
    assertNotNull("getHelpString returned null.", checkHelp);
  }

  /**
   * A utility method that returns a string (yyyy-MM-dd) representing the date before today.
   * 
   * @param calendar A Calendar object representing today's date.
   * @return A string in the form yyyy-MM-dd representing the date yesterday.
   */
  private String generateYesterday(Calendar calendar) {
    // Roll back the date.
    calendar.add(Calendar.DATE, -1);
    int year = calendar.get(Calendar.YEAR);
    // Account for Calendar months starting at 0 instead of 1.
    int month = calendar.get(Calendar.MONTH) + 1;
    int day = calendar.get(Calendar.DAY_OF_MONTH);
    String monthString;
    String dayString;
    
    if (month < 10) {
      monthString = "0" + month;
    }
    else {
      monthString = Integer.toString(month);
    }
    if (day < 10) {
      dayString = "0" + day;
    }
    else {
      dayString = Integer.toString(day);
    }
    return year + "-" + monthString + "-" + dayString;
  }
}
